Asp.Net Viewstate Viewer - Felix John COLIBRI. |
- abstract : the structure and display of the Viewstate hidden field incorporated in Asp.Net exchanges
- key words : ASP.NET - ViewState - Stateless protocol - Viewer
- software used : Windows XP, Asp.Net 1.1 - Delphi 2005
- hardware used : Pentium 2.800Mhz, 512 M memory, 250 G hard disc
- scope : Asp Net 1.1, 2.0, Mono, Delphi 8, 2005, 2005
- level : Asp.Net / Delphi developer
- plan :
1 - ViewState Viewer This simple utility displays the content of the "viewstate" field, which is a
state carrier of any Asp.Net page. The HTTP protocol is "stateless": the Server does not maintain the state of a Client request, since usually there are many Clients, and managing the state
of each of them would overburden the Server and make any scalability impossible. So the Client must include state information with each request, and the Server sends back this state to the Client.
In Asp.Net, this state information in encoded in a viewstate field. The content is coded. So the purpose of this paper is to analyze the viewstate content. Also let us mention that this paper presents ASP.NET 1.1 viewstate analysis.
The content of ASP.NET 2.0 viewer is slightly different (see the Reference section)
2 - The Viewstate Analyzer Source Code 2.1 - The Viewstate structure
The Viewstate field is not crypted. But it has a 2 level coding: - first all HTML punctuations, like "<", "+", "=" are encoded using a %nn notation. For instance, "+" is coded as "%2B" (where $2B is the Hex value of the ASCII code of ".")
- then the text is "base 64" encoded: to fit all 256 possible byte value in 7 bits, there is a transcoding from 8 to 7 bits.
The resulting decoded string follows a "compact xml" structure, where the
elements are all between "<" and ">" and the elements of the tags are triples, pairs, lists arrays and indexes. The IEBNF (Indented Extended BNF) grammar of this coding is
viewstate= element .
element = triple | pair | list | index .
triple= 't<' element ';' element ';' element '>' .
pair= 'p<' element ';' element '>' .
list= 'l<' { element ';' } .
index= 'i<' value '>' .
value= ANY_BUT ( ';' | '>' ) .
|
In a schematic way, the structure looks like:
Here is the content of a viewstate field (from the Asp.Net example presented below):
__VIEWSTATE=dDwxMTg3NDQ2OTMwO3Q8O2w8aTwxPjs%2BO2w8dD w7bDxpPDE%2BOz47bDx0PHQ8O3A8bDxpPDA%2BO2k8MT47aTwyPj tpPDM%2BO2k8ND47aTw1PjtpPDY% 2BO2k8Nz47aTw4Pjs%2BO2w8cDxBbXN0ZXJkYW07QW0%2BO3A8Qm FyY2Vsb25hO0JhPjtwPEZyYW5rZn
VydDtGcj47cDxHZW5ldmE7R2U%2BO3A8TG9uZG9uO0xvPjtwPE1pb GFubztNaT47cDxOZXcgWW9yaztO eT47cDxQYXJpcztQYT47cDxTeWRuZXk7c3k%2BOz4%2BOz47Oz47P j47Pj47PjxlNdt3PuVJT7gpuLcE 0Er9eNDy |
2.2 - The Delphi Source Code The routine which removes the %nn part is the following:
function f_convert_special_characters(p_string: String): String;
var l_hex_string: ShortString;
l_index: Integer; begin
l_index:= 1; Result:= '';
while l_index<= Length(p_string) do
begin
if p_string[l_index]= '%'
then begin
// -- change specials %26 into &
l_hex_string:= '$00';
l_hex_string[2]:= p_string[l_index+ 1];
l_hex_string[3]:= p_string[l_index+ 2];
Result:= Result+ Chr(StrToInt(l_hex_string));
Inc(l_index, 2);
end
else Result:= Result+ p_string[l_index];
Inc(l_index); end; // while
end; // f_convert_special_characters |
dDwxMTg3NDQ2OTMwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpP DE+Oz47bDx0PHQ8O3A8bDxpPDA+O2k8MT47 aTwyPjtpPDM+O2k8ND47aTw1PjtpPDY+O2k8Nz47aTw4P js+O2w8cDxBbXN0ZXJkYW07QW0+O3A8QmFy Y2Vsb25hO0JhPjtwPEZyYW5rZnVydDtGcj47cDxHZW5ld
mE7R2U+O3A8TG9uZG9uO0xvPjtwPE1pbGFu bztNaT47cDxOZXcgWW9yaztOeT47cDxQYXJpcztQYT47c DxTeWRuZXk7c3k+Oz4+Oz47Oz47Pj47Pj47 PjxlNdt3PuVJT7gpuLcE0Er9eNDy |
We are using the following CLASS to decode the base 64 string:
c_base_64_uuencode= class(c_basic_object)
m_trace_uuencode: Boolean;
Constructor create_base_64_uuencode(p_name: String);
procedure trace_uuencode(p_text: String);
procedure convert_stream_to_base_64_strings(p_c_stream: tStream;
p_c_base_64_strings: tStrings);
procedure convert_base_64_strings_to_stream(p_c_base_64_strings: tStrings;
p_c_stream: tStream;
p_raise_exception_or_pad: Boolean);
end; // c_base_64_uuencode |
The detail of the decode method is in the .zip And here is the result of decoding our viewstate:
t<1187446930;t<;l<i<1>;>;l<t<;l<i<1>;>;l<t<t<; p<l<i<0>;i<1>;i<2>;i<3>;i<4>;i<5>;
i<6>;i<7>;i<8>;>;l<p<Amsterdam;Am>;p<Barcelona;Ba>; p<Frankfurt;Fr>;p<Geneva;Ge>; p<London;Lo>;p<Milano;Mi>;p<New
York;Ny>;p<Paris;Pa>;p<Sydney;sy>;>>;>;;>;>>;>>; ><e5Ûw>åIO¸)¸·ÐJýxÐò| | The triple / pair / list / index structure is already noticeable in this content.
To decode the "compact xml" format, we are using a recursive parsing routine which is the following:
procedure analyze_recursive(p_level: Integer; p_title: String);
procedure add_result(p_text: String);
begin
if Trim(p_text)<> ''
then m_c_result_list.Add(f_spaces(p_level* 2)+ p_text);
end; // add_result procedure analyze_triplet;
// -- "t<xxx;yyy;zzz>" begin
Inc(l_index, 2);
analyze_recursive(p_level+ 1, ' t1 ');
check_and_skip(';');
analyze_recursive(p_level+ 1,' t2 ');
check_and_skip(';');
analyze_recursive(p_level+ 1,' t3 ');
// -- here should be on "<" Inc(l_index);
end; // analyze_triplet procedure analyze_pair;
// -- "p<xxx;yyy>" begin
Inc(l_index, 2);
analyze_recursive(p_level+ 1,'p1');
// -- here on ";" check_and_skip(';');
analyze_recursive(p_level+ 1,'p2');
// -- here on ";" check_and_skip('>');
end; // analyze_pair procedure analyze_list;
// -- "l<xxx;yyy;zzz;>"
var l_list: String; begin
Inc(l_index, 2);
if (l_index+ 1<= l_length) and (m_viewstate_string[l_index+ 1]= '<')
then begin
repeat
analyze_recursive(p_level+ 1,'list');
check_and_skip(';');
until (l_index>= l_length) or (m_viewstate_string[l_index]= '>');
l_list:= ''; end
else begin
l_list:= f_string_skip_until_match(m_viewstate_string, '>', l_index);
end; // -- here on ">"
Inc(l_index); end; // analyze_list
procedure analyze_index;
var l_index_string: String; begin
Inc(l_index, 2);
l_index_string:= f_string_skip_until(m_viewstate_string, '>', l_index);
add_result(l_index_string); // -- here on ">"
check_and_skip('>'); end; // analyze_index
procedure analyze_until_semi_colon;
var l_content: String; begin
l_content:= f_string_skip_until_in(m_viewstate_string, [';', '>'], l_index);
add_result(l_content);
end; // analyze_until_semi_colon begin // analyze_recursive
if f_is_triplet
then analyze_triplet else
if f_is_pair
then analyze_pair else
if f_is_list
then analyze_list else
if f_is_index then analyze_index
else analyze_until_semi_colon;
end; // analyze_recursive | and the test of the triplet, pair, list or index parts are simply tested by
checking the "x<" presence. This is the triple detection function:
function f_is_triplet: Boolean; begin
Result:= (l_index+ 1<= l_length)
and (m_viewstate_string[l_index]= 't')
and (m_viewstate_string[l_index+ 1]= '<')
end; // f_is_triplet |
The (partial) trace of the analysis is:
>[ 1] analyze_recursive (start)t >[ 1] analyze_triplet t
>[ 3] analyze_recursive ( t1 )1 >[ 3] analyze_until_; 1 <[ 13] analyze_until_; 1187446930
<[ 13] analyze_recursive | And the collected viewstate content is (partial):
1187446930 1 1 0 1 2 ...
Amsterdam Am Barcelona Ba |
2.3 - simple example Let's analyze the viewstate content of a simple page request and submit. Our page will simply - display a Combobox with towns where we hold our Asp.Net trainings
- and a TextBox for the user e-mail
- When the user clicks the "submit" button, we will send him back the next session date.
This mock-up page is a fake one, but of course, our trainings are not !
Here is how we build the Asp.Net application: | start Delphi | |
select "file | new | asp.net application" | |
Delphi presents the path / file / server dialog | | type the requested information: | | then, in the top right tree project manager, right click the WebForm1.aspx line, and rename the file name "a_asp_net_trainings.aspx"
| | in the top left structure tree, rename the CLASS
| | compile to make sure everything is set up correctly |
| Delphi compiles, starts Cassini, loads Internet Explorer which displays our (empty) application |
| close Internet Explorer | |
open up NotePad and type the following training locations: Am=Amsterdam Ba=Barcelona Fr=Frankfurt Ge=Geneva
Lo=London Mi=Milano Ny=New York Pa=Paris sy=Sydney | and save the file in the Asp.Net directory under "towns.txt" |
| drop a DropDownList on the Form, and rename it "town_dropdownlist_" | |
load the towns in town_dropdownlist_ in the Init event: - declare the method and import System.Io:
interface type Tasp_net_training_form =
class(System.Web.UI.Page)
// ... private
procedure load_combobox_from_file(p_c_dropdown_list: DropDownList;
p_full_file_name: String);
end; // Tasp_net_training_form implementation
uses System.IO; | - write the loading code:
procedure Tasp_net_training_form.load_combobox_from_file(
p_c_dropdown_list: DropDownList; p_full_file_name: String);
var l_c_stream_reader: StreamReader;
l_town_name_and_value: array of String;
l_town_name: String;
l_town_value: String; begin
if System.IO.File.Exists(p_full_file_name)
then begin l_c_stream_reader :=
StreamReader.Create(System.IO.File.OpenRead(p_full_file_name));
try
while l_c_stream_reader.Peek > -1 do
begin
l_town_name_and_value :=
l_c_stream_reader.ReadLine.Split(['=']);
l_town_name := l_town_name_and_value[0];
l_town_value := l_town_name_and_value[1];
p_c_dropdown_list.Items.Add(ListItem.Create(l_town_value,
l_town_name));
end; finally
l_c_stream_reader.Close;
end; // try finally
end; // file exists
end; // load_combobox_from_file | - call this method in the OnInit event:
const k_town_file_name= 'towns.txt';
procedure Tasp_net_training_form.OnInit(e: EventArgs);
begin InitializeComponent;
inherited OnInit(e);
load_combobox_from_file(town_dropdownlist_,
Request.PhysicalApplicationPath+ k_town_file_name);
town_dropdownlist_.Items.FindByText('New York').Selected := True;
end; // OnInit | | |
add some text to the .HTML in the PageLoad event:
procedure Tasp_net_training_form.Page_Load(sender: System.Object;
e: System.EventArgs); begin
if IsPostBack
then Response.Write('<BR><BR>PostBack :')
else Response.Write('<BR><BR>Welcome '+ k_town_file_name);
end; // Page_Load | | |
add a Button to the Form
procedure Tasp_net_trainings_form.Button1_Click(sender: System.Object;
e: System.EventArgs);
var l_date_time: System.DateTime; begin
l_date_time:= System.DateTime.Now.AddDays(30);
Response.Write('Next Asp.Net session: '+ Convert.ToString(l_date_time));
end; // Button1_Click | | |
compile and run | | Delphi starts Cassini, loads Internet Explorer which displays our application:
| | select "Sydney", for instance, and click "Button" |
| the submission is sent to Cassini which answers back: |
Now the viewstate analysis part: | to capture the answer viewstate, in Internet Explorer select "display | source" |
| the ascii content of the .HTML is displayed in Notepad, included the viewstate field (hilighted) : |
| copy the viewstate in the ClipBoard, start the viewstate analyzer application, paste the viewstate in the upper Memo, and click "decode":
| | click "analyze" |
| the content of the viewstate is displayed: |
2.4 - Improvements
Just a couple of points: - using Internet Explorer "view source" only displays the incoming viewstates. To capture the outgoing viewstate, we should capture the viewstate in the .Pas file on the Server.
The other solution (which we generally use) is to capture the packets flowing between the Internet Explorer and the IIS or Cassini Server. In fact we have incorporated the c_viewstate_analyzer in our
Cassini Packet Capture project, so we can monitor all file transfers between the Client and the Server Here is the full data exchange (the viewstates have been split in several
lines, and NOT decoded in this snapshot): 0 ====================================== 345   -> | GET
/p_asp_net_trainings/a_asp_net_trainings.aspx HTTP/1.1   -> | Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*   -> | Accept-Language: fr
  -> | Accept-Encoding: gzip, deflate   -> | User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)   -> | Host: localhost:81
  -> | Connection: Keep-Alive   -> |   -> | --------------------------------------279 <-   | HTTP/1.1 200 OK
<-   | Server: Cassini/1.0.0.0 <-   | Date: Thu, 16 Mar 2006 07:45:21 GMT <-   | X-AspNet-Version: 1.1.4322 <-   | Set-Cookie:
ASP.NET_SessionId=ea40eceyu5j4s1mexanyan55; path=/ <-   | Cache-Control: private <-   | Content-Type: text/html; charset=utf-8
<-   | Content-Length: 1343 <-   | Connection: Close <-   | <-   | --------------------------------------1344
<-   | <BR><BR>Welcome towns.txt <-   | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <-   |
<-   | <html> <-   | <head> <-   | <title></title> <-   | </head>
<-   | <body ms_positioning="GridLayout"> <-   | <form name="_ctl0" method="post" action="a_asp_net_trainings.aspx" id="_ctl0">
<-   | <input type="hidden" name="__VIEWSTATE" value="dDwxMTg3NDQ2OTMwO3Q8O2w8aTwxPjs+O2w 8dDw7bDxpPDE+Oz47bDx0PHQ8O3A8bDxpPDA+O2k8M T47aTwyPjtpPDM+O2k8ND47aTw1PjtpPDY+O2k8
Nz47aTw4Pjs+O2w8cDxBbXN0ZXJkYW07QW0+O3A8Qm FyY2Vsb25hO0JhPjtwPEZyYW5rZnVydDtGcj47c DxHZW5ldmE7R2U+O3A8TG9uZG9uO0xvPjtwPE1pbGF ubztNaT47cDxOZXcgWW9yaztOeT47cDxQYXJpcz tQYT47cDxTeWRuZXk7c3k+Oz4+Oz47Oz47Pj47Pj47
PjxlNdt3PuVJT7gpuLcE0Er9eNDy" /> <-   | <-   | <select name="town_dropdownlist_" id="town_dropdownlist_" style="Z-INDEX: 1; LEFT: 14px; POSITION: absolute; TOP: 14px">
<-   | <option value="Am">Amsterdam</option> <-   | <option value="Ba">Barcelona</option>
<-   | <option value="Fr">Frankfurt</option> <-   | <option value="Ge">Geneva</option>
<-   | <option value="Lo">London</option> <-   | <option value="Mi">Milano</option>
<-   | <option selected="selected" value="Ny">New York</option> <-   | <option value="Pa">Paris</option>
<-   | <option value="sy">Sydney</option> <-   | <-   | </select>
<-   | <input type="submit" name="Button1" value="Button" id="Button1" style="Z-INDEX: 2; LEFT: 254px; POSITION: absolute; TOP: 14px" /> <-   | </form>
<-   | </body> <-   | </html> <-   | 1 ====================================== 985   -> | POST
/p_asp_net_trainings/a_asp_net_trainings.aspx HTTP/1.1   -> | Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*   -> | Referer:
http://localhost:81/p_asp_net_trainings/a_asp_net_trainings.aspx   -> | Accept-Language: fr   -> | Content-Type: application/x-www-form-urlencoded
  -> | Accept-Encoding: gzip, deflate   -> | User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)   -> | Host: localhost:81
  -> | Content-Length: 417   -> | Connection: Keep-Alive   -> | Cache-Control: no-cache   -> | Cookie:
ASP.NET_SessionId=ea40eceyu5j4s1mexanyan55   -> |   -> | __VIEWSTATE=dDwxMTg3NDQ2OTMwO3Q8O2w8aTwxPjs%2BO2w8dD w7bDxpPDE%2BOz47bDx0PHQ8O3A8bDxpPDA%2BO2k8MT47aTwyPj
tpPDM%2BO2k8ND47aTw1PjtpPDY% 2BO2k8Nz47aTw4Pjs%2BO2w8cDxBbXN0ZXJkYW07QW0%2BO3A8Qm FyY2Vsb25hO0JhPjtwPEZyYW5rZn VydDtGcj47cDxHZW5ldmE7R2U%2BO3A8TG9uZG9uO0xvPjtwPE1p bGFubztNaT47cDxOZXcgWW9yaztO
eT47cDxQYXJpcztQYT47cDxTeWRuZXk7c3k%2BOz4%2BOz47Oz47 Pj47Pj47PjxlNdt3PuVJT7gpuLcE 0Er9eNDy&town_dropdownlist_=sy&Button1=Button --------------------------------------215 <-   | HTTP/1.1 200 OK
<-   | Server: Cassini/1.0.0.0 <-   | Date: Thu, 16 Mar 2006 07:45:30 GMT <-   | X-AspNet-Version: 1.1.4322
<-   | Cache-Control: private <-   | Content-Type: text/html; charset=utf-8 <-   | Content-Length: 1377
<-   | Connection: Close <-   | <-   | --------------------------------------1378
<-   | <BR><BR>PostBack :Next Asp.Net session: 15/04/2006 08:45:30 <-   | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<-   | <-   | <html> <-   | <head> <-   | <title></title>
<-   | </head> <-   | <body ms_positioning="GridLayout"> <-   | <form name="_ctl0" method="post"
action="a_asp_net_trainings.aspx" id="_ctl0"> <-   | <input type="hidden" name="__VIEWSTATE" value="dDwxMTg3NDQ2OTMwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpP DE+Oz47bDx0PHQ8O3A8bDxpPDA+O
2k8MT47aTwyPjtpPDM+O2k8ND47aTw1PjtpPDY+O2k8Nz47aTw4P js+O2w8cDxBbXN0ZXJkYW07QW0+O 3A8QmFyY2Vsb25hO0JhPjtwPEZyYW5rZnVydDtGcj47cDxHZW5ld mE7R2U+O3A8TG9uZG9uO0xvPjtwP E1pbGFubztNaT47cDxOZXcgWW9yaztOeT47cDxQYXJpcztQYT47c
DxTeWRuZXk7c3k+Oz4+Oz47Oz47P j47Pj47PjxlNdt3PuVJT7gpuLcE0Er9eNDy" /> <-   | <-   | <select name="town_dropdownlist_"
id="town_dropdownlist_" style="Z-INDEX: 1; LEFT: 14px; POSITION: absolute; TOP: 14px"> <-   | <option value="Am">Amsterdam</option>
<-   | <option value="Ba">Barcelona</option> <-   | <option value="Fr">Frankfurt</option>
<-   | <option value="Ge">Geneva</option> <-   | <option value="Lo">London</option>
<-   | <option value="Mi">Milano</option> <-   | <option value="Ny">New York</option>
<-   | <option value="Pa">Paris</option> <-   | <option selected="selected" value="sy">Sydney</option> <-   |
<-   | </select> <-   | <input type="submit" name="Button1" value="Button" id="Button1" style="Z-INDEX: 2; LEFT: 254px; POSITION: absolute; TOP: 14px" />
<-   | </form> <-   | </body> <-   | </html> |
- the exercise clearly demonstrates that the viewstate only contains
- whatever the .HTML protocol does not transfers
- anything the Asp.Net developper added to the viewstate (not displayed)
In our case, the .HTML already contained the DropDownList items (in red):
<BR><BR>PostBack :Next Asp.Net session: 15/04/2006 07:33:42 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title></title>
</head> <body ms_positioning="GridLayout"> <form name="_ctl0" method="post" action="a_asp_net_trainings.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxMTg3 ... gpuLcE0Er9eNDy" />
<select name="town_dropdownlist_" id="town_dropdownlist_" style="Z-INDEX: 1; LEFT: 14px; POSITION: absolute; TOP: 14px">
<option value="Am">Amsterdam</option> <option value="Ba">Barcelona</option>
<option value="Fr">Frankfurt</option> <option value="Ge">Geneva</option>
<option value="Lo">London</option> <option value="Mi">Milano</option>
<option value="Ny">New York</option> <option value="Pa">Paris</option>
<option selected="selected" value="sy">Sydney</option> </select>
<input type="submit" name="Button1" value="Button" id="Button1" style="Z-INDEX: 2; LEFT: 254px; POSITION: absolute; TOP: 14px" />
</form> </body> </html> | but .HTML could not code the "key=value" list which is an Asp.Net feature.
So this part was placed in the viewstate (in blue, partial) - we could improve the decoding of the viewstate. For instance, we could try to link the displayed values to the Form controls.
3 - Download the Sources We will not Here are the source code files: The .ZIP file(s) contain: - the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any
other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP - are self-contained: you will not need any other product (unless expressly mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder.
The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse etc. This notation is presented in the Alsacian Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate:
the more traffic and Google references we get, the more articles we will write.
4 - References We first found the Viewstate structure in the following page:
5 - The author Felix John COLIBRI works at the Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |